/*
Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { test, expect } from "../../element-web-test";
import { SettingLevel } from "../../../src/settings/SettingLevel";

const CtrlOrMeta = process.platform === "darwin" ? "Meta" : "Control";

test.describe("Composer", () => {
    test.use({
        displayName: "Janet",
    });

    test.use({
        room: async ({ app, user }, use) => {
            const roomId = await app.client.createRoom({ name: "Composing Room" });
            await app.viewRoomByName("Composing Room");
            await use({ roomId });
        },
    });

    test.beforeEach(async ({ room }) => {}); // trigger room fixture

    test.describe("Rich text editor", () => {
        test.use({
            labsFlags: ["feature_wysiwyg_composer"],
        });

        test.describe("Commands", () => {
            // TODO add tests for rich text mode

            test.describe("Plain text mode", () => {
                test("autocomplete behaviour tests", async ({ page }) => {
                    // Select plain text mode after composer is ready
                    await expect(page.locator("div[contenteditable=true]")).toBeVisible();
                    await page.getByRole("button", { name: "Hide formatting" }).click();

                    // Typing a single / displays the autocomplete menu and contents
                    await page.getByRole("textbox").press("/");

                    // Check that the autocomplete options are visible and there are more than 0 items
                    await expect(page.getByTestId("autocomplete-wrapper")).not.toBeEmpty();

                    // Entering `//` or `/ ` hides the autocomplete contents
                    // Add an extra slash for `//`
                    await page.getByRole("textbox").press("/");
                    await expect(page.getByTestId("autocomplete-wrapper")).toBeEmpty();
                    // Remove the extra slash to go back to `/`
                    await page.getByRole("textbox").press("Backspace");
                    await expect(page.getByTestId("autocomplete-wrapper")).not.toBeEmpty();
                    // Add a trailing space for `/ `
                    await page.getByRole("textbox").press(" ");
                    await expect(page.getByTestId("autocomplete-wrapper")).toBeEmpty();

                    // Typing a command that takes no arguments (/devtools) and selecting by click works
                    await page.getByRole("textbox").press("Backspace");
                    await page.getByRole("textbox").pressSequentially("dev");
                    await page.getByTestId("autocomplete-wrapper").getByText("/devtools").click();
                    // Check it has closed the autocomplete and put the text into the composer
                    await expect(page.getByTestId("autocomplete-wrapper")).not.toBeVisible();
                    await expect(page.getByRole("textbox").getByText("/devtools")).toBeVisible();
                    // Send the message and check the devtools dialog appeared, then close it
                    await page.getByRole("button", { name: "Send message" }).click();
                    await expect(page.getByRole("dialog").getByText("Developer Tools")).toBeVisible();
                    await page.getByRole("button", { name: "Close dialog" }).click();

                    // Typing a command that takes arguments (/spoiler) and selecting with enter works
                    await page.getByRole("textbox").pressSequentially("/spoil");
                    await expect(page.getByTestId("autocomplete-wrapper").getByText("/spoiler")).toBeVisible();
                    await page.getByRole("textbox").press("Enter");
                    // Check it has closed the autocomplete and put the text into the composer
                    await expect(page.getByTestId("autocomplete-wrapper")).not.toBeVisible();
                    await expect(page.getByRole("textbox").getByText("/spoiler")).toBeVisible();
                    // Enter some more text, then send the message
                    await page.getByRole("textbox").pressSequentially("this is the spoiler text ");
                    await page.getByRole("button", { name: "Send message" }).click();
                    // Check that a spoiler item has appeared in the timeline and locator the spoiler command text
                    await expect(page.locator("button.mx_EventTile_spoiler")).toBeVisible();
                    await expect(page.getByText("this is the spoiler text")).toBeVisible();
                });
            });
        });

        test.describe("Mentions", () => {
            // TODO add tests for rich text mode

            test.describe("Plain text mode", () => {
                test.use({
                    botCreateOpts: {
                        displayName: "Bob",
                    },
                });

                // https://github.com/vector-im/element-web/issues/26037
                test.skip("autocomplete behaviour tests", async ({ page, app, bot: bob }) => {
                    // Set up a private room so we have another user to mention
                    await app.client.createRoom({
                        is_direct: true,
                        invite: [bob.credentials.userId],
                    });
                    await app.viewRoomByName("Bob");

                    // Select plain text mode after composer is ready
                    await expect(page.locator("div[contenteditable=true]")).toBeVisible();
                    await page.getByRole("button", { name: "Hide formatting" }).click();

                    // Typing a single @ does not display the autocomplete menu and contents
                    await page.getByRole("textbox").press("@");
                    await expect(page.getByTestId("autocomplete-wrapper")).toBeEmpty();

                    // Entering the first letter of the other user's name opens the autocomplete...
                    await page.getByRole("textbox").pressSequentially(bob.credentials.displayName.slice(0, 1));
                    // ...with the other user name visible, and clicking that username...
                    await page.getByTestId("autocomplete-wrapper").getByText(bob.credentials.displayName).click();
                    // ...inserts the username into the composer
                    const pill = page.getByRole("textbox").getByText(bob.credentials.displayName, { exact: false });
                    await expect(pill).toHaveAttribute("contenteditable", "false");
                    await expect(pill).toHaveAttribute("data-mention-type", "user");

                    // Send the message to clear the composer
                    await page.getByRole("button", { name: "Send message" }).click();

                    // Typing an @, then other user's name, then trailing space closes the autocomplete
                    await page.getByRole("textbox").pressSequentially(`@${bob.credentials.displayName} `);
                    await expect(page.getByTestId("autocomplete-wrapper")).toBeEmpty();

                    // Send the message to clear the composer
                    await page.getByRole("button", { name: "Send message" }).click();

                    // Moving the cursor back to an "incomplete" mention opens the autocomplete
                    await page
                        .getByRole("textbox")
                        .pressSequentially(`initial text @${bob.credentials.displayName.slice(0, 1)} abc`);
                    await expect(page.getByTestId("autocomplete-wrapper")).toBeEmpty();
                    // Move the cursor left by 4 to put it to: `@B| abc`, check autocomplete displays
                    await page.getByRole("textbox").press("LeftArrow");
                    await page.getByRole("textbox").press("LeftArrow");
                    await page.getByRole("textbox").press("LeftArrow");
                    await page.getByRole("textbox").press("LeftArrow");
                    await expect(page.getByTestId("autocomplete-wrapper")).not.toBeEmpty();

                    // Selecting the autocomplete option using Enter inserts it into the composer
                    await page.getByRole("textbox").press("Enter");
                    const pill2 = page.getByRole("textbox").getByText(bob.credentials.displayName, { exact: false });
                    await expect(pill2).toHaveAttribute("contenteditable", "false");
                    await expect(pill2).toHaveAttribute("data-mention-type", "user");
                });
            });
        });

        test("sends a message when you click send or press Enter", async ({ page }) => {
            // Type a message
            await page.locator("div[contenteditable=true]").pressSequentially("my message 0");
            // It has not been sent yet
            await expect(page.locator(".mx_EventTile_body", { hasText: "my message 0" })).not.toBeVisible();

            // Click send
            await page.getByRole("button", { name: "Send message" }).click();
            // It has been sent
            await expect(page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my message 0")).toBeVisible();

            // Type another
            await page.locator("div[contenteditable=true]").pressSequentially("my message 1");
            // Send message
            page.locator("div[contenteditable=true]").press("Enter");
            // It was sent
            await expect(page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my message 1")).toBeVisible();
        });

        test("sends only one message when you press Enter multiple times", async ({ page }) => {
            // Type a message
            await page.locator("div[contenteditable=true]").pressSequentially("my message 0");
            // It has not been sent yet
            await expect(page.locator(".mx_EventTile_body", { hasText: "my message 0" })).not.toBeVisible();

            // Click send
            await page.locator("div[contenteditable=true]").press("Enter");
            await page.locator("div[contenteditable=true]").press("Enter");
            await page.locator("div[contenteditable=true]").press("Enter");
            // It has been sent
            await expect(page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my message 0")).toBeVisible();
            await expect(page.locator(".mx_EventTile_last .mx_EventTile_body")).toHaveCount(1);
        });

        test("can write formatted text", async ({ page }) => {
            await page.locator("div[contenteditable=true]").pressSequentially("my ");
            await page.locator("div[contenteditable=true]").press(`${CtrlOrMeta}+KeyB`);
            await page.locator("div[contenteditable=true]").pressSequentially("bold");
            await page.locator("div[contenteditable=true]").press(`${CtrlOrMeta}+KeyB`);
            await page.locator("div[contenteditable=true]").pressSequentially(" message");
            await page.getByRole("button", { name: "Send message" }).click();
            await expect(page.locator(".mx_EventTile_body strong").getByText("bold")).toBeVisible();
        });

        test.describe("when Control+Enter is required to send", () => {
            test.beforeEach(async ({ app }) => {
                await app.settings.setValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true);
            });

            test("only sends when you press Control+Enter", async ({ page }) => {
                // Type a message and press Enter
                await page.locator("div[contenteditable=true]").pressSequentially("my message 3");
                await page.locator("div[contenteditable=true]").press("Enter");
                // It has not been sent yet
                await expect(page.locator(".mx_EventTile_body", { hasText: "my message 3" })).not.toBeVisible();

                // Press Control+Enter
                await page.locator("div[contenteditable=true]").press("Control+Enter");
                // It was sent
                await expect(
                    page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my message 3"),
                ).toBeVisible();
            });
        });

        test.describe("links", () => {
            test("create link with a forward selection", async ({ page }) => {
                // Type a message
                await page.locator("div[contenteditable=true]").pressSequentially("my message 0");
                await page.locator("div[contenteditable=true]").press(`${CtrlOrMeta}+A`);

                // Open link modal
                await page.getByRole("button", { name: "Link" }).click();
                // Fill the link field
                await page.getByRole("textbox", { name: "Link" }).pressSequentially("https://matrix.org/");
                // Click on save
                await page.getByRole("button", { name: "Save" }).click();
                // Send the message
                await page.getByRole("button", { name: "Send message" }).click();

                // It was sent
                await expect(page.locator(".mx_EventTile_body a").getByText("my message 0")).toBeVisible();
                await expect(page.locator(".mx_EventTile_body a")).toHaveAttribute(
                    "href",
                    new RegExp("https://matrix.org/"),
                );
            });
        });
    });
});
